﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System.Threading.Tasks;
using Microsoft.Media.ISO;
using Microsoft.Media.CaptureClient;
using Windows.Storage.Streams;
using System.Threading;
using Windows.Storage;

namespace Microsoft.Media.IngestClient.UnitTests
{
    [TestClass]
    public class UnitTests
    {
        const uint OutputTimeScale = 10000000;

        [TestMethod]
        public async Task FragmentReserialization()
        {
            var dataFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("data");
            var inputFolder = await dataFolder.GetFolderAsync("input");
            foreach (var file in await inputFolder.GetFilesAsync())
            {
                await TestReserialization(file);
            }
        }

        [TestMethod]
        public async Task IsmvReserialization()
        {
            var dataFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("data");
            var file = await dataFolder.GetFileAsync("output.ismv");
            await TestReserialization(file);
        }

        [TestMethod]
        public async Task Transmux()
        {
            const ulong timeStampOffset = 14069091986181750;
            var dataFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("data");
            var inputFolder = await dataFolder.GetFolderAsync("input");
            var tracks = new List<IEncodedTrack>();
            PiffHeader piffHeader = null;
            IList<PiffFragment> piffFragments = new List<PiffFragment>();
            PiffFooter piffFooter;
            ulong[] absoluteTimes = new ulong[3];

            foreach (var file in (await inputFolder.GetFilesAsync()).OrderBy(f => f.Name))
            {
                var filenameParts = file.Name.Split(new[] { '-', '.' });
                var fragmentIndex = uint.Parse(filenameParts[0]);
                var streamIndex = uint.Parse(filenameParts[1]);

                using (var inputStream = await file.OpenReadAsync())
                {
                    var boxes = MP4BoxReader.ReadAllBoxes(inputStream.AsStream()).ToList();
                    if (fragmentIndex == 0)
                    {
                        switch (streamIndex)
                        {
                            case 0:
                                tracks.Add(new EncodedVideoTrack(GetVideoEncodingProfileHigh(), boxes));
                                break;
                            case 1:
                                tracks.Add(new EncodedVideoTrack(GetVideoEncodingProfileLow(), boxes));
                                break;
                            case 2:
                                tracks.Add(new EncodedAudioTrack(GetAudioEncodingProfile(), boxes));
                                break;
                        }
                        if (tracks.Count == 3)
                        {
                            piffHeader = PiffTransmuxer.CreateHeader(tracks, OutputTimeScale);
                        }
                    }

                    var moov = boxes.OfType<MovieBox>().First();
                    var duration = moov.MovieHeader.Duration * 10000000 / moov.MovieHeader.TimeScale;
                    var piffFragment = PiffTransmuxer.CreateFragment(boxes, streamIndex + 1, (uint)(streamIndex + 1 + fragmentIndex * 3), absoluteTimes[streamIndex] + timeStampOffset, duration, OutputTimeScale);
                    piffFragments.Add(piffFragment);
                    absoluteTimes[streamIndex] += duration;
                }
            }

            piffFooter = PiffTransmuxer.CreateFooter();

            var liveClient = new LiveClient();
            using (var ras = new MemoryStream())
            {
                liveClient.Initialize(new StreamOutputConsumer(ras, false));
                liveClient.Start(piffHeader);
                foreach (var fragment in piffFragments)
                {
                    liveClient.PushFragment(fragment);
                }
                liveClient.Complete(piffFooter);
                await liveClient.FinalizeAsync(CancellationToken.None);

                var file = await dataFolder.GetFileAsync("output.ismv");
                using (var dest = await file.OpenReadAsync())
                {
                    Assert.IsTrue(dest.Size > 0);
                    // the order of the fragments will be slightly off so instead just check the size (which should be identical).
                    Assert.IsTrue((ulong)ras.Length == dest.Size);
                }
            }
        }

        [TestMethod]
        public async Task PushClient()
        {
            using (var ras = new MemoryStream())
            {
                ulong totalBytes = 0;
                var pushClient = new PushClient();
                var t = pushClient.SendBoxesAsync(new StreamOutputConsumer(ras, true), CancellationToken.None, new Progress<BoxHistory>());
                for (int i = 0; i < 100; i++)
                {
                    var box = new MediaDataBox();
                    box.Data = new byte[i * 10];
                    ulong size = box.CalculateBoxSize();
                    Assert.IsTrue(size > 0);
                    totalBytes += size;
                    pushClient.PushBox(box, null);
                }
                Assert.IsTrue(totalBytes > 0);
                pushClient.PushComplete();
                await t;
                Assert.IsTrue(totalBytes == (ulong)ras.Length);
            }
        }

        [TestMethod]
        public async Task PushClientAsync()
        {
            using (var ras = new MemoryStream())
            {
                ulong totalBytes = 0;
                var pushClient = new PushClient();
                var t = pushClient.SendBoxesAsync(new StreamOutputConsumer(ras, false), CancellationToken.None, new Progress<BoxHistory>());
                for (int i = 0; i < 100; i++)
                {
                    var box = new MediaDataBox();
                    box.Data = new byte[i * 10];
                    ulong size = box.CalculateBoxSize();
                    Assert.IsTrue(size > 0);
                    totalBytes += size;
                    await pushClient.PushBoxAsync(box, null, CancellationToken.None);
                }
                Assert.IsTrue(totalBytes > 0);
                pushClient.PushComplete();
                await t;
                Assert.IsTrue(totalBytes == (ulong)ras.Length);
            }
        }

        [TestMethod]
        public async Task PushClientCancel()
        {
            using (var ras = new MemoryStream())
            {
                var pushClient = new PushClient();
                CancellationTokenSource cts = new CancellationTokenSource();
                var t = pushClient.SendBoxesAsync(new StreamOutputConsumer(ras, false), cts.Token, new Progress<BoxHistory>());
                var box = new MediaDataBox();
                box.Data = new byte[1000];
                pushClient.PushBox(box, null);
                cts.Cancel();
                pushClient.PushBox(box, null); // StreamOutputConsumer requires more data to be pushed before cancellation token will get recognized.
                try
                {
                    await t;
                }
                catch (OperationCanceledException)
                {
                    return;
                }
                Assert.Fail();
            }
        }

        [TestMethod]
        public async Task PushClientFail()
        {
            var ras = new MemoryStream();
            var pushClient = new PushClient();
            var t = pushClient.SendBoxesAsync(new StreamOutputConsumer(ras, false), CancellationToken.None, new Progress<BoxHistory>());
            var box = new MediaDataBox();
            box.Data = new byte[1000];
            pushClient.PushBox(box, null);
            ras.Dispose(); // this should cause the task to fail.
            pushClient.PushBox(box, null);
            try
            {
                await t;
                Assert.Fail();
            }
            catch (OperationCanceledException)
            {
                Assert.Fail();
            }
            catch { /* success */ }
        }

        static VideoEncodingProperties GetVideoEncodingProfileHigh()
        {
            var videoTrackProperties = new VideoEncodingProperties();
            videoTrackProperties.Subtype = MediaEncodingSubtypes.H264;
            videoTrackProperties.FrameRate.Numerator = 15;
            videoTrackProperties.FrameRate.Denominator = 1;
            videoTrackProperties.PixelAspectRatio.Numerator = 1;
            videoTrackProperties.PixelAspectRatio.Denominator = 1;
            videoTrackProperties.Width = 640;
            videoTrackProperties.Height = 480;
            videoTrackProperties.Bitrate = 699000;
            return videoTrackProperties;
        }

        static VideoEncodingProperties GetVideoEncodingProfileLow()
        {
            var videoTrackProperties = new VideoEncodingProperties();
            videoTrackProperties.Subtype = MediaEncodingSubtypes.H264;
            videoTrackProperties.FrameRate.Numerator = 15;
            videoTrackProperties.FrameRate.Denominator = 1;
            videoTrackProperties.PixelAspectRatio.Numerator = 1;
            videoTrackProperties.PixelAspectRatio.Denominator = 1;
            videoTrackProperties.Width = 320;
            videoTrackProperties.Height = 240;
            videoTrackProperties.Bitrate = 174000;
            return videoTrackProperties;
        }

        static AudioEncodingProperties GetAudioEncodingProfile()
        {
            var audioTrackProperties = new AudioEncodingProperties();
            audioTrackProperties.Subtype = MediaEncodingSubtypes.Aac;
            audioTrackProperties.SampleRate = 44100;
            audioTrackProperties.ChannelCount = 1;
            audioTrackProperties.BitsPerSample = 16;
            audioTrackProperties.Bitrate = 96000;
            return audioTrackProperties;
        }

        static async Task TestReserialization(StorageFile file)
        {
            using (var inputStream = await file.OpenReadAsync())
            {
                using (var outputStream = new MemoryStream())
                {
                    var boxes = MP4BoxReader.ReadAllBoxes(inputStream.AsStream()).ToList();
                    MP4BoxWriter.WriteAllBoxes(boxes, outputStream);
                    Assert.IsTrue(AreEqual(inputStream.AsStreamForRead(), outputStream));
                }
            }
        }

        static bool AreEqual(Stream stream1, Stream stream2)
        {
            if (stream1.Length != stream2.Length) return false;
            stream1.Seek(0, SeekOrigin.Begin);
            stream2.Seek(0, SeekOrigin.Begin);
            for (int i = 0; i < stream1.Length; i++)
            {
                var sourceByte = stream1.ReadByte();
                var destByte = stream2.ReadByte();
                if (sourceByte != destByte)
                {
                    return false;
                }
            }
            return true;
        }
    }
}
